import * as vscode from 'vscode';
import * as path from 'path';
import * as os from 'os';
import { MagicApiClient, MagicGroupMetaRaw } from '../magicApiClient';
import { MAGIC_RESOURCE_TYPES, MagicResourceType } from '../types';
import { MirrorMeta, MirrorFileMeta } from './types';

/**
 * 镜像元数据管理器
 * 处理所有元数据相关的操作
 */
export class MirrorMetaManager {
    private toPosix(rel: string): string {
        return rel.replace(/\\/g, '/');
    }

    // 读取镜像元数据
    public async readMirrorMeta(folder: vscode.Uri): Promise<MirrorMeta | null> {
        try {
            const metaUri = vscode.Uri.joinPath(folder, '.magic-api-mirror.json');
            const buf = await vscode.workspace.fs.readFile(metaUri);
            const text = Buffer.from(buf).toString('utf8');
            return JSON.parse(text);
        } catch {
            return null;
        }
    }

    // 写入镜像元数据
    public async writeMirrorMeta(root: vscode.Uri, meta: MirrorMeta): Promise<void> {
        const dirUri = root;
        await this.ensureDir(dirUri);
        const metaUri = vscode.Uri.joinPath(root, '.magic-api-mirror.json');
        await vscode.workspace.fs.writeFile(metaUri, Buffer.from(JSON.stringify(meta, null, 2), 'utf8'));
    }

    // 计算某个代码文件的元数据文件名/路径
    public getMetaFileUriFor(root: vscode.Uri, type: MagicResourceType, groupPathSub: string | undefined, fileName: string): vscode.Uri {
        const dirUri = vscode.Uri.joinPath(root, type, ...(groupPathSub ? groupPathSub.split('/') : []));
        const metaName = `.${fileName}.meta.json`;
        return vscode.Uri.joinPath(dirUri, metaName);
    }

    // 计算某分组目录的分组元数据文件路径 (.group-meta.json)
    public getGroupMetaFileUriFor(root: vscode.Uri, type: MagicResourceType, groupPathSub: string | undefined): vscode.Uri {
        const dirUri = vscode.Uri.joinPath(root, type, ...(groupPathSub ? groupPathSub.split('/') : []));
        const metaName = `.group-meta.json`;
        return vscode.Uri.joinPath(dirUri, metaName);
    }

    // 读取某分组目录的分组元数据（仅使用 .group-meta.json）
    public async readLocalGroupMeta(root: vscode.Uri, type: MagicResourceType, groupPathSub: string | undefined): Promise<MagicGroupMetaRaw | null> {
        const dirUri = vscode.Uri.joinPath(root, type, ...(groupPathSub ? groupPathSub.split('/') : []));
        const metaUri = vscode.Uri.joinPath(dirUri, '.group-meta.json');
        try {
            const buf = await vscode.workspace.fs.readFile(metaUri);
            const text = Buffer.from(buf).toString('utf8');
            const obj = JSON.parse(text);
            return obj as MagicGroupMetaRaw;
        } catch {
            return null;
        }
    }

    // 读取某文件对应的本地元数据
    public async readLocalMeta(root: vscode.Uri, type: MagicResourceType, groupPathSub: string | undefined, fileName: string): Promise<MirrorFileMeta | null> {
        const metaUri = this.getMetaFileUriFor(root, type, groupPathSub, fileName);
        try {
            const buf = await vscode.workspace.fs.readFile(metaUri);
            const text = Buffer.from(buf).toString('utf8');
            const obj = JSON.parse(text);
            return obj as MirrorFileMeta;
        } catch {
            return null;
        }
    }

    // 写入某文件对应的本地元数据
    public async writeLocalMeta(root: vscode.Uri, meta: MirrorFileMeta): Promise<void> {
        const segs = meta.groupPath.split('/').filter(Boolean);
        const type = segs[0] as MagicResourceType;
        const groupPathSub = segs.slice(1).join('/');
        // 在落盘前，计算并注入 autoGeneration.fullUrlPath（始终基于本地分组递归计算），并清理 extra 仅保留 autoGeneration
        try {
            const extra: any = (meta as any).extra || {};
            const ag: any = extra.autoGeneration || {};
            // 优先使用已有的 fullUrlPath（从服务器资源树中获取），避免重复计算
            if (!ag.fullUrlPath || typeof ag.fullUrlPath !== 'string') {
                const fullUrl = await this.computeFullUrlPath(root, type, groupPathSub, meta);
                ag.fullUrlPath = fullUrl;
            }
            // 只保留 autoGeneration，移除其他扩展字段
            (meta as any).extra = { autoGeneration: ag };
        } catch(e) {
            console.error('writeLocalMeta error', e);
        }
        const metaUri = this.getMetaFileUriFor(root, type, groupPathSub, meta.name);
        // 确保目录存在
        const dirUri = vscode.Uri.joinPath(root, type, ...(groupPathSub ? groupPathSub.split('/') : []));
        await this.ensureDir(dirUri);
        await vscode.workspace.fs.writeFile(metaUri, Buffer.from(JSON.stringify(meta, null, 2), 'utf8'));
    }

    // 写入某分组目录的分组元数据 (.group-meta.json)
    public async writeLocalGroupMeta(
        root: vscode.Uri,
        type: MagicResourceType,
        groupPathSub: string | undefined,
        meta: MagicGroupMetaRaw,
        opts?: { refreshChildren?: boolean }
    ): Promise<void> {
        const metaUri = this.getGroupMetaFileUriFor(root, type, groupPathSub);
        const dirUri = vscode.Uri.joinPath(root, type, ...(groupPathSub ? groupPathSub.split('/') : []));
        await this.ensureDir(dirUri);
        await vscode.workspace.fs.writeFile(metaUri, Buffer.from(JSON.stringify(meta, null, 2), 'utf8'));
        // 分组 path 变化会影响当前目录及其子目录下所有文件的 fullUrlPath，逐一更新对应文件的本地 meta（可选）
        try {
            const shouldRefresh = opts?.refreshChildren !== false;
            if (shouldRefresh) {
                await this.refreshFullUrlPathsUnder(root, type, groupPathSub);
            }
        } catch(e) {console.warn(e)}
    }

    // 统一写入拦截器：写入脚本并更新对应的本地 meta（含时间戳）
    public async writeLocalScriptAndUpdateMeta(
        root: vscode.Uri,
        type: MagicResourceType,
        groupPathSub: string | undefined,
        fileName: string,
        script: string,
        extraMeta?: Partial<MirrorFileMeta>
    ): Promise<void> {
        const dirUri = vscode.Uri.joinPath(root, type, ...(groupPathSub ? groupPathSub.split('/') : []));
        const ext = type === 'datasource' ? '.json' : '.ms';
        const msUri = vscode.Uri.joinPath(dirUri, `${fileName}${ext}`);
        const groupPath = `${type}${groupPathSub ? '/' + groupPathSub : ''}`;

        // 并行执行：确保目录存在 + 读取现有 meta（如果有）
        const [_, existing] = await Promise.all([
            this.ensureDir(dirUri),
            this.readLocalMeta(root, type, groupPathSub, fileName).catch(() => null)
        ]);

        // 并行写入：脚本文件 + 元数据文件
        const merged: MirrorFileMeta = this.touchLocalMetaTime({
            ...(existing || { name: fileName, type, groupPath }),
            ...(extraMeta || {}),
            groupPath,
            name: (extraMeta?.name ?? existing?.name) || fileName,
            type,
        });

        await Promise.all([
            vscode.workspace.fs.writeFile(msUri, Buffer.from(script, 'utf8')),
            this.writeLocalMeta(root, merged)
        ]);
    }

    // 从服务器文件信息生成本地元数据对象
    public toMirrorMetaFromServer(info: any): MirrorFileMeta {
        const rawSegs = String(info.groupPath || '').split('/').filter(Boolean);
        const typeCandidate = (rawSegs[0] || info.type) as MagicResourceType;
        const type = (MAGIC_RESOURCE_TYPES.includes(typeCandidate) ? typeCandidate : (info.type as MagicResourceType));
        // 规范化：去除重复的类型前缀，仅保留一个类型前缀
        const segs = [...rawSegs];
        while (segs[0] === type) segs.shift();
        const groupPathSub = segs.join('/');
        const groupPath = [type, groupPathSub].filter(Boolean).join('/');
        const name = String(info.name || '').replace(/\.ms$/, '');
        const meta: MirrorFileMeta = {
            id: String(info.id || ''),
            name,
            type,
            groupId: info.groupId ? String(info.groupId) : undefined,
            groupPath,
            path: info.path || undefined,
            method: info.method || undefined,
            requestMapping: (info.requestMapping ?? info.path) || undefined,
            description: info.description || undefined,
            locked: (info.locked ?? info.lock) || undefined,
            createTime: info.createTime,
            updateTime: info.updateTime,
        };

        // 注入类型专属字段（API/任务等），并保留服务端原始扩展字段
        const rawExtra: any = (info as any).extra ?? (info as any).properties ?? {};
        const rawTop: any = info as any;
        if (type === 'api') {
            meta.params = (rawExtra.params ?? rawExtra.parameters ?? rawTop.params ?? rawTop.parameters) ?? undefined;
            meta.headers = (rawExtra.headers ?? rawTop.headers) ?? undefined;
            meta.contentType = (rawExtra.contentType ?? rawTop.contentType) ?? undefined;
            const timeoutVal = (rawExtra.timeout ?? rawTop.timeout);
            meta.timeout = (typeof timeoutVal === 'number') ? timeoutVal : undefined;
        } else if (type === 'task') {
            meta.cron = (rawExtra.cron ?? rawTop.cron) ?? undefined;
            const enabledVal = (rawExtra.enabled ?? rawTop.enabled);
            meta.enabled = (typeof enabledVal !== 'undefined') ? !!enabledVal : undefined;
            const eosVal = (rawExtra.executeOnStart ?? rawTop.executeOnStart);
            meta.executeOnStart = (typeof eosVal !== 'undefined') ? !!eosVal : undefined;
        }

        try {
            const extraCopy: Record<string, any> = { ...(rawExtra || {}) };
            // 如果服务器信息中包含预计算的 fullUrlPath，则保存到 autoGeneration 中
            if (info.fullUrlPath && typeof info.fullUrlPath === 'string') {
                const ag: any = extraCopy.autoGeneration || {};
                ag.fullUrlPath = info.fullUrlPath;
                extraCopy.autoGeneration = ag;
            }
            // 保留服务器端原始扩展字段用于比较（不删除任何字段）
            if (Object.keys(extraCopy).length > 0) {
                (meta as any).extra = extraCopy;
            }
        } catch(e) {console.warn(e)}

        return meta;
    }

    public touchLocalMetaTime(meta: MirrorFileMeta): MirrorFileMeta {
        try {
            const copy: MirrorFileMeta = { ...meta };
            // 写入本地时间戳、用户名到 extra.autoGeneration
            const extra: any = (copy as any).extra || {};
            const ag: any = extra.autoGeneration || {};
            ag.localUpdateTime = Date.now();
            try {
                const user = os.userInfo().username || process.env.USERNAME || process.env.USER || '';
                if (user) ag.localUsername = user;
            } catch(e) {console.warn(e)}
            extra.autoGeneration = ag;
            (copy as any).extra = extra;
            // 保留扩展字段用于稳定比较；兼容旧字段但不再更新顶层 localUpdateTime
            return copy;
        } catch {
            return meta;
        }
    }

    // 读取本地修改时间（兼容从 extra.autoGeneration/localUpdateTime 或旧字段）
    public getLocalUpdateTime(meta: MirrorFileMeta | null | undefined): number {
        try {
            const ag = (meta as any)?.extra?.autoGeneration;
            if (ag && typeof ag.localUpdateTime === 'number') return ag.localUpdateTime;
        } catch(e) {console.warn(e)}
        return meta && typeof (meta as any).localUpdateTime === 'number' ? (meta as any).localUpdateTime as number : 0;
    }

    // 计算完整 URL（包含分组 .group-meta.json 的 path 前缀与文件自身的 path/requestMapping）
    private async computeFullUrlPath(root: vscode.Uri, type: MagicResourceType, groupPathSub: string | undefined, meta: MirrorFileMeta): Promise<string> {
        // 递归读取分组 path 前缀
        const prefixSegs: string[] = [];
        if (groupPathSub) {
            const segParts = groupPathSub.split('/').filter(Boolean);
            for (let i = 1; i <= segParts.length; i++) {
                const cur = segParts.slice(0, i).join('/');
                const gmeta = await this.readLocalGroupMeta(root, type, cur);
                const gp = gmeta?.path;
                if (gp && typeof gp === 'string') {
                    const cleaned = gp.replace(/^\/+/, '').replace(/\/+$/, '');
                    if (cleaned) prefixSegs.push(cleaned);
                }
            }
        }
        // 叶子路径：API 优先 requestMapping 其次 path；其他类型优先 path
        const baseName = meta?.name || '';
        let leafRaw: string;
        if (type === 'api') {
            const p0 = (meta as any)?.requestMapping || (meta as any)?.path || `/${baseName}`;
            leafRaw = p0.startsWith('/') ? p0.substring(1) : p0;
        } else {
            const p1 = (meta as any)?.path || `/${baseName}`;
            leafRaw = p1.startsWith('/') ? p1.substring(1) : p1;
        }
        const urlPath = '/' + [prefixSegs.join('/'), leafRaw].filter(Boolean).join('/');
        return urlPath.replace(/\/+/, '/');
    }

    // 遍历指定分组目录（含子目录），为每个 .meta.json 重新计算 fullUrlPath 并更新写入
    private async refreshFullUrlPathsUnder(root: vscode.Uri, type: MagicResourceType, groupPathSub: string | undefined): Promise<void> {
        const baseDir = vscode.Uri.joinPath(root, type, ...(groupPathSub ? groupPathSub.split('/') : []));
        await this.ensureDir(baseDir);
        const stack: { base: vscode.Uri; segs: string[] }[] = [{ base: baseDir, segs: (groupPathSub ? groupPathSub.split('/').filter(Boolean) : []) }];
        while (stack.length) {
            const cur = stack.pop()!;
            let entries: [string, vscode.FileType][] = [];
            try { entries = await vscode.workspace.fs.readDirectory(cur.base); } catch { entries = []; }
            for (const [name, ft] of entries) {
                if (ft === vscode.FileType.Directory) {
                    stack.push({ base: vscode.Uri.joinPath(cur.base, name), segs: [...cur.segs, name] });
                } else if (ft === vscode.FileType.File && name.endsWith('.meta.json') && name.startsWith('.')) {
                    const fileName = name.replace(/^\./, '').replace(/\.meta\.json$/, '');
                    const sub = cur.segs.join('/');
                    try {
                        const meta = await this.readLocalMeta(root, type, sub, fileName);
                        if (meta) {
                            const fullUrl = await this.computeFullUrlPath(root, type, sub, meta);
                            const extra: any = (meta as any).extra || {};
                            const ag: any = extra.autoGeneration || {};
                            ag.fullUrlPath = fullUrl;
                            extra.autoGeneration = ag;
                            (meta as any).extra = extra;
                            await this.writeLocalMeta(root, meta);
                        }
                    } catch(e) {console.warn(e)}
                }
            }
        }
    }

    public sanitizeMetaForDiff(meta: MirrorFileMeta | null): Record<string, any> | null {
        if (!meta) return null;
        const m: any = { ...meta };
        const locked = (typeof m.locked !== 'undefined') ? !!m.locked : ((typeof m.lock !== 'undefined') ? !!m.lock : undefined);
        const requestMapping = (m.requestMapping ?? m.path) ?? undefined;
        const out: Record<string, any> = {
            id: m.id,
            name: m.name,
            type: m.type,
            groupId: m.groupId,
            groupPath: m.groupPath,
            path: m.path,
            method: m.method,
            requestMapping,
            description: m.description,
            locked,
            createTime: m.createTime,
            updateTime: m.updateTime,
            params: m.params,
            headers: m.headers,
            contentType: m.contentType,
            timeout: (typeof m.timeout === 'number') ? m.timeout : undefined,
            cron: m.cron,
            enabled: (typeof m.enabled !== 'undefined') ? !!m.enabled : undefined,
            executeOnStart: (typeof m.executeOnStart !== 'undefined') ? !!m.executeOnStart : undefined,
        };
        // 清理本地字段与不必要字段（如 extra/localUpdateTime 等）仅用于比较的规范化输出
        // 注意：不修改原始对象，仅在比较时输出规范化 JSON
        for (const k of Object.keys(out)) {
            const v = (out as any)[k];
            if (typeof v === 'undefined' || v === '') delete (out as any)[k];
        }
        return out;
    }

    public stableStringify(obj: any, space: number = 0): string {
        const sortRec = (val: any): any => {
            if (Array.isArray(val)) return val.map(sortRec);
            if (val && typeof val === 'object') {
                const keys = Object.keys(val).sort((a, b) => a.localeCompare(b));
                const out: Record<string, any> = {};
                for (const k of keys) out[k] = sortRec(val[k]);
                return out;
            }
            return val;
        };
        return JSON.stringify(sortRec(obj), null, space);
    }

    public validateLocalMeta(meta: MirrorFileMeta, fileName?: string): { ok: boolean; errors: string[] } {
        const errors: string[] = [];
        if (!meta) errors.push('缺少元数据');
        if (!meta.name) errors.push('缺少名称');
        if (!meta.type || !MAGIC_RESOURCE_TYPES.includes(meta.type)) errors.push('类型非法');
        if (!meta.groupPath) errors.push('缺少分组路径');
        if (fileName && meta.name !== fileName) errors.push('名称与文件名不一致');
        return { ok: errors.length === 0, errors };
    }

    private async ensureDir(uri: vscode.Uri): Promise<void> {
        try {
            await vscode.workspace.fs.stat(uri);
        } catch {
            await vscode.workspace.fs.createDirectory(uri);
        }
    }

    public compareMeta(local: MirrorFileMeta | null, server: MirrorFileMeta | null): { equal: boolean; left: Record<string, any>; right: Record<string, any> } {
        const left = this.sanitizeMetaForDiff(local) || {};
        const right = this.sanitizeMetaForDiff(server) || {};
        const equal = this.stableStringify(left) === this.stableStringify(right);
        return { equal, left, right };
    }

    public buildMergeJsons(local: MirrorFileMeta | null, server: MirrorFileMeta | null, space: number = 2): { leftJson: string; rightJson: string } {
        const { left, right } = this.compareMeta(local, server);
        const leftJson = this.stableStringify(left, space);
        const rightJson = this.stableStringify(right, space);
        return { leftJson, rightJson };
    }
}